package com.wang.tool.monitor.ops;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPath;
import com.wang.tool.monitor.core.LinkInfo;
import com.wang.tool.monitor.core.MonitorInfo;
import com.wang.tool.monitor.fastover.FastoverConfig;
import com.wang.tool.monitor.fastover.FastoverFunction;
import com.wang.tool.monitor.fastover.FastoverInfo;
import com.wang.tool.util.CommonUtil;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @Description 简易ops实现
 * @Author shaopeng
 * @Date 2022/10/19
 * @Version 1.0
 */
public class SimpleOps implements MonitorOps {

    /**
     * 源 非空
     * 变更时根据source.equals判断是否有更新,防止多次调用产生无意义刷新
     */
    private final Object source;

    /**
     * 生效开始时间
     */
    private Long beginTime;

    /**
     * 生效结束时间
     */
    private Long endTime;


    /**
     * 快速结束判定器
     * eg: 降级,下线,模拟场景
     * 1.正常执行业务逻辑返回null
     * 2.返回FastoverInfo对象,自行抛异常/返回目标对象
     */
    private FastoverFunction fastoverFunction;


    /**
     * debug判定器
     * eg: 动态debug
     * 调试可用
     * 注意开启范围及性能
     */
    private Predicate<OpsContext> debugPredict;

    /**
     * 慢调用阈值
     * 建议不要太严苛
     *
     * @see MonitorInfo#slowTime
     */
    private Integer slowTime;

    /**
     * 超时阈值
     * 一般在给上游接入层接口设置,我们不知道上游给我们的时间
     * 依据上游配置
     *
     * @see MonitorInfo#outTime
     */
    private Integer outTime;

    /**
     * 附件提取器
     * 返回值会写入载荷
     * debug状态下detail也会写入链路ext中
     * 注意开启范围及性能
     *
     * @see MonitorInfo#details
     * @see LinkInfo#ext
     * @see MonitorInfo#stepOutMonitor
     */
    private Function<OpsContext, Object> detailFetcher;


    public SimpleOps(Object source) {
        if (source == null) {
            throw new IllegalArgumentException("source can not be null");
        }
        this.source = source;
    }


    @Override
    public FastoverInfo fastover(long time, Object[] params) {
        return fastoverFunction == null ? null : fastoverFunction.fastover(time, params);
    }

    @Override
    public boolean isDebug(OpsContext opsContext) {
        if (debugPredict != null) {
            return debugPredict.test(opsContext);
        }
        return opsContext.isDebug();
    }

    @Override
    public Object fetchDetail(OpsContext opsContext) {
        return detailFetcher == null ? null : detailFetcher.apply(opsContext);
    }

    @Override
    public Object getSource() {
        return source;
    }

    @Override
    public Integer fetchSlowTime(Object[] params) {
        return slowTime;
    }

    @Override
    public Integer fetchOutTime(Object[] params) {
        return outTime;
    }

    public FastoverFunction getFastoverFunction() {
        return fastoverFunction;
    }

    public void setFastoverFunction(FastoverFunction fastoverFunction) {
        this.fastoverFunction = fastoverFunction;
    }

    public Predicate<OpsContext> getDebugPredict() {
        return debugPredict;
    }

    public void setDebugPredict(Predicate<OpsContext> debugPredict) {
        this.debugPredict = debugPredict;
    }

    public Integer getSlowTime() {
        return slowTime;
    }

    public void setSlowTime(Integer slowTime) {
        this.slowTime = slowTime;
    }

    public Integer getOutTime() {
        return outTime;
    }

    public void setOutTime(Integer outTime) {
        this.outTime = outTime;
    }

    public Function<OpsContext, Object> getDetailFetcher() {
        return detailFetcher;
    }

    public void setDetailFetcher(Function<OpsContext, Object> detailFetcher) {
        this.detailFetcher = detailFetcher;
    }

    @Override
    public Long getBeginTime() {
        return beginTime;
    }

    public void setBeginTime(Long beginTime) {
        this.beginTime = beginTime;
    }

    @Override
    public Long getEndTime() {
        return endTime;
    }

    public void setEndTime(Long endTime) {
        this.endTime = endTime;
    }

    @Override
    public String toString() {
        return "MonitorOps{source=" + source + "}";
    }


    /**
     * 解析配置成控制器
     *
     * @param source
     * @return
     * @throws IllegalArgumentException
     */
    public static MonitorOps parse(SimpleOps.OpsSource source) throws IllegalArgumentException {
        if (source == null) {
            return null;
        }
        SimpleOps ops = new SimpleOps(source);
        ops.setSlowTime(source.getSlowTime());
        ops.setOutTime(source.getOutTime());

        //动态debug
        ops.setDebugPredict(DebugConfig.parseDebugPredict(source.getDebugConfig()));

        //快速返回
        ops.setFastoverFunction(FastoverConfig.parseFastoverFunction(source.getFastoverConfig()));

        //动态载荷
        ops.setDetailFetcher(DetailConfig.parseDetailFetcher(source.getDetailConfig()));
        return ops;
    }

    /**
     * fastjson适用非标准jsonpath 使用场景不会很复杂,遂不引入标准json-path,减少依赖
     * <p>
     * 1.过滤语句只支持下一级，不支持多级连续，支持复杂条件，是否存在key  ?()
     * 支持：$[@.test>1]
     * 不支持：$[@.test.n>1]
     * 支持：$[name='1' && test=1 || ?( t2=3 || group='group') ]
     * <p>
     * 2.过滤语句仅支持@,不支持$,支持 && ||
     * 支持：$[@.name='1' && test=1 && t2=3]
     *
     * @see OpsContext
     */
    public static class OpsSource {

        /**
         * 动态debug配置
         *
         * @see MonitorOps#isDebug
         */
        private DebugConfig debugConfig;

        /**
         * 快速返回配置
         *
         * @see MonitorOps#fastover
         */
        private FastoverConfig fastoverConfig;

        /**
         * 慢调用阈值
         *
         * @see MonitorInfo#slowTime
         */
        private Integer slowTime;

        /**
         * 超时阈值
         *
         * @see MonitorInfo#outTime
         */
        private Integer outTime;

        /**
         * 动态载荷配置
         *
         * @see MonitorOps#fetchDetail
         */
        private DetailConfig detailConfig;

        public DetailConfig getDetailConfig() {
            return detailConfig;
        }

        public void setDetailConfig(DetailConfig detailConfig) {
            this.detailConfig = detailConfig;
        }

        public Integer getSlowTime() {
            return slowTime;
        }

        public void setSlowTime(Integer slowTime) {
            this.slowTime = slowTime;
        }

        public Integer getOutTime() {
            return outTime;
        }

        public void setOutTime(Integer outTime) {
            this.outTime = outTime;
        }


        public DebugConfig getDebugConfig() {
            return debugConfig;
        }

        public void setDebugConfig(DebugConfig debugConfig) {
            this.debugConfig = debugConfig;
        }

        public FastoverConfig getFastoverConfig() {
            return fastoverConfig;
        }

        public void setFastoverConfig(FastoverConfig fastoverConfig) {
            this.fastoverConfig = fastoverConfig;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            OpsSource opsSource = (OpsSource) o;
            return Objects.equals(fastoverConfig, opsSource.fastoverConfig) && Objects.equals(debugConfig, opsSource.debugConfig) && Objects.equals(slowTime, opsSource.slowTime) && Objects.equals(outTime, opsSource.outTime) && Objects.equals(detailConfig, opsSource.detailConfig);
        }

        @Override
        public int hashCode() {
            return Objects.hash(fastoverConfig, debugConfig, slowTime, outTime, detailConfig);
        }

        @Override
        public String toString() {
            return "OpsSource{" +
                    "fastoverConfig=" + fastoverConfig +
                    ", debugConfig=" + debugConfig +
                    ", slowTime=" + slowTime +
                    ", outTime=" + outTime +
                    ", detailConfig=" + detailConfig +
                    '}';
        }
    }

    /**
     * 动态debug配置
     */
    public static class DebugConfig {

        /**
         * 开始小时
         * 小时级生效,从小时的0分开始
         * 2022010113
         */
        private String beginHour;

        /**
         * 结束小时
         * 小时级生效,至小时的59分结束
         * 2022010113
         */
        private String endHour;

        /**
         * 参数源
         */
        private List<String> sources;

        /**
         * 白名单
         */
        private Set<String> whites;


        public static Predicate<OpsContext> parseDebugPredict(DebugConfig debugConfig) {
            if (debugConfig == null) {
                return null;
            }

            if (CommonUtil.isEmpty(debugConfig.getWhites()) || CommonUtil.isEmpty(debugConfig.getSources())) {
                return null;
            }

            try {
                SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHH");
                Date begin = format.parse(debugConfig.getBeginHour());
                if (begin == null) {
                    return null;
                }
                Date end;
                if (CommonUtil.isBlank(debugConfig.getEndHour())) {
                    end = begin;
                } else {
                    end = format.parse(debugConfig.getEndHour());
                }
                //涵盖那个小时
                end.setTime(end.getTime() + 3600 * 1000);
                if (end.getTime() <= begin.getTime() || System.currentTimeMillis() >= end.getTime()) {
                    return null;
                }

                List<JSONPath> compiledSources = debugConfig.getSources()
                        .stream()
                        .map(s -> {
                            JSONPath compile = JSONPath.compile(s);
                            compile.eval(new Object[]{});
                            return compile;
                        }).collect(Collectors.toList());
                return opsContext -> isOpen(begin.getTime(), end.getTime(), opsContext.getParams(), compiledSources, debugConfig.getWhites());
            } catch (Throwable e) {
                throw new IllegalArgumentException("debugConfig is error, debugConfig:" + JSON.toJSONString(debugConfig));
            }
        }

        /**
         * 是否开启
         *
         * @return
         */
        private static boolean isOpen(long beginTime, long endTime, Object[] params, List<JSONPath> compiledSources, Set<String> whites) {
            long now = System.currentTimeMillis();
            if (beginTime > now || endTime < now) {
                return false;
            }
            for (JSONPath source : compiledSources) {
                Object val = source.eval(params);
                if (val == null) {
                    continue;
                }
                //集合
                if (val instanceof Collection) {
                    if(((Collection<?>) val).stream()
                            .anyMatch(s -> whites.contains(String.valueOf(s)))){
                        return true;
                    }
                }else if(whites.contains(val.toString())){
                    return true;
                }
            }
            return false;
        }

        public String getBeginHour() {
            return beginHour;
        }

        public void setBeginHour(String beginHour) {
            this.beginHour = beginHour;
        }

        public String getEndHour() {
            return endHour;
        }

        public void setEndHour(String endHour) {
            this.endHour = endHour;
        }


        public List<String> getSources() {
            return sources;
        }

        public void setSources(List<String> sources) {
            this.sources = sources;
        }

        public Set<String> getWhites() {
            return whites;
        }

        public void setWhites(Set<String> whites) {
            this.whites = whites;
        }

        @Override
        public String toString() {
            return "DebugConfig{" +
                    "beginHour='" + beginHour + '\'' +
                    ", endHour='" + endHour + '\'' +
                    ", sources=" + sources +
                    ", whites=" + whites +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            DebugConfig that = (DebugConfig) o;
            return Objects.equals(beginHour, that.beginHour) && Objects.equals(endHour, that.endHour) && Objects.equals(sources, that.sources) && Objects.equals(whites, that.whites);
        }

        @Override
        public int hashCode() {
            return Objects.hash(beginHour, endHour, sources, whites);
        }


    }


    /**
     * 详情配置
     */
    public static class DetailConfig {

        /**
         * 期望状态码
         * 如果目标状态码和此不一致也会进入specialAttachments
         */
        private String expectStatus;

        /**
         * 特殊情况载荷计算器
         * 错 慢 非预期状态 debug
         * jsonpath语法,返回值无限制
         * jsonpath.eval("$.params");
         *
         * @see MonitorOps#fetchDetail
         * @see OpsContext
         */
        private List<String> specialDetail;


        /**
         * 采样比例
         * 0-100
         */
        private Integer sampleRate;

        /**
         * 采样场景附件计算器
         * jsonpath语法,返回值无限制
         * jsonpath.eval("$.params");
         *
         * @see MonitorOps#fetchDetail
         * @see OpsContext
         */
        private List<String> sampleDetail;


        public static Function<OpsContext, Object> parseDetailFetcher(DetailConfig detailConfig) {
            if (detailConfig == null) {
                return null;
            }
            List<String> sampleDetail = detailConfig.getSampleDetail();
            List<String> specialDetail = detailConfig.getSpecialDetail();
            if (CommonUtil.isEmpty(specialDetail) && CommonUtil.isEmpty(sampleDetail)) {
                return null;
            }

            return new Function<OpsContext, Object>() {
                final List<JSONPath> specials = CommonUtil.isEmpty(specialDetail) ? null : specialDetail.stream()
                        .map(s -> {
                            JSONPath compile = JSONPath.compile(s);
                            compile.eval(new OpsContext());
                            return compile;
                        }).collect(Collectors.toList());
                final List<JSONPath> samples = CommonUtil.isEmpty(sampleDetail) ? null : sampleDetail.stream().map(s -> {
                    JSONPath compile = JSONPath.compile(s);
                    compile.eval(new OpsContext());
                    return compile;
                }).collect(Collectors.toList());
                final Integer sampleRate = detailConfig.getSampleRate();
                final String expectStatus = CommonUtil.isBlank(detailConfig.getExpectStatus()) ? null : detailConfig.getExpectStatus();

                @Override
                public Object apply(OpsContext opsContext) {
                    //特殊场景
                    if (specials != null) {
                        //错 慢 抓异常 状态非预期 debug
                        if (opsContext.isImplicitError() || opsContext.isImplicitSlow()
                                || (expectStatus != null && !expectStatus.equals(opsContext.getStatus()))
                                || opsContext.isDebug()
                        ) {
                            if (specials.size() == 1) {
                                return specials.get(0).eval(opsContext);
                            }
                            return specials.stream().map(special -> special.eval(opsContext)).collect(Collectors.toList());
                        }
                    }

                    if (samples == null) {
                        return null;
                    }

                    //采样率达标,根据根时间戳取余采样
                    boolean sampling = sampleRate != null && (sampleRate >= 100 || opsContext.getRootBeginTime() % 100 < sampleRate);
                    if (!sampling) {
                        return null;
                    }
                    if (samples.size() == 1) {
                        return samples.get(0).eval(opsContext);
                    }
                    return samples.stream().map(simple -> simple.eval(opsContext)).collect(Collectors.toList());
                }
            };
        }

        public String getExpectStatus() {
            return expectStatus;
        }

        public void setExpectStatus(String expectStatus) {
            this.expectStatus = expectStatus;
        }

        public List<String> getSpecialDetail() {
            return specialDetail;
        }

        public void setSpecialDetail(List<String> specialDetail) {
            this.specialDetail = specialDetail;
        }

        public Integer getSampleRate() {
            return sampleRate;
        }

        public void setSampleRate(Integer sampleRate) {
            this.sampleRate = sampleRate;
        }

        public List<String> getSampleDetail() {
            return sampleDetail;
        }

        public void setSampleDetail(List<String> sampleDetail) {
            this.sampleDetail = sampleDetail;
        }

        @Override
        public String toString() {
            return "DetailConfig{" +
                    "expectStatus='" + expectStatus + '\'' +
                    ", specialDetail=" + specialDetail +
                    ", sampleRate=" + sampleRate +
                    ", sampleDetail=" + sampleDetail +
                    '}';
        }
    }

}
